<?php
/**
 * @category Goatherd
 * @package Zend_Session
 *
 * @author Copyright (c) 2012 Maik Penz <maik@phpkuh.de>
 * @version $Id: MongoDb.php 77 2012-03-27 07:53:54Z maik@phpkuh.de $
 *
 * Derived from:
 * http://gertonscorner.googlecode.com/svn/trunk/phplib/GcLib/Zend/Session/SaveHandler/MongoDb.php
 * Gerton ten Ham (MIT License; 2012-02-10)
 * AND
 * \Zend_Session_SaveHandle_DbTable
 * (BSD License; Id: 24593 2012-01-05 20:35:02Z matthew)
 */
namespace Goatherd;

/**
 * Connection must be added through method call.
 *
 * @todo implement allowLifetimeOverride (for security reasons)
 * @todo quality check; are predefined column names good practice at all?
 *
 * @category Goatherd
 * @package Zend_Session
 * @subpackage SaveHandler
 */
class Zend_Session_SaveHandler_MongoDb
implements \Zend_Session_SaveHandler_Interface
{
    const NAME                  = 'name';
    const LIFETIME              = 'lifetime';
    const MODIFIED_COLUMN       = 'modifiedColumn';
    const LIFETIME_COLUMN       = 'lifetimeColumn';
    const DATA_COLUMN           = 'dataColumn';

    const PRIMARY_ASSIGNMENT_SESSION_ID        = 'sessionId';

    /**
    * Session collection last modification time column
    *
    * @var string
    */
    protected $_modifiedColumn     = 'modified_at';

    /**
     * Session collection lifetime column
     *
     * @var string
     */
    protected $_lifetimeColumn     = 'lifetime';

    /**
     * Session collection data column
     *
     * @var string
     */
    protected $_dataColumn         = 'data';

    /**
     * Session collection primary id column
     *
     * @var string
     */
    protected $_idColumn           = self::PRIMARY_ASSIGNMENT_SESSION_ID;

    /**
     *
     * @var string
     */
    const DEFAULT_COLLECTION_NAME = 'session';

    /**
     * MongoDb connection
     *
     * @var Mongo
     */
    protected $_conn = null;

    /**
     * Mongo database
     *
     * @var MongoDB
     */
    protected $_db = null;

    /**
     * Mongo collection
     *
     * @var MongoCollection
     */
    protected $_collection = null;

    /**
    * Session lifetime
    *
    * @var int
    */
    protected $_lifetime = false;

    /**
     * Collection name.
     *
     * @var string
     */
    protected $_name = self::DEFAULT_COLLECTION_NAME;

    /**
     * Session save path
     *
     * @var string
     */
    protected $_sessionSavePath;

    /**
     * Session name
     *
     * @var string
     */
    protected $_sessionName;

    /**
     * Constructor
     *
     * @param array|Zend_Config $config
     * @throws \Zend_Session_SaveHandler_Exception
     * @return void
     */
    public function __construct($config) {
        if (!is_array($config) || !($config instanceof \Zend_Config)) {
            throw new \Zend_Session_SaveHandler_Exception(
                '$config must be an instance of Zend_Config or array of key/value pairs containing '
              . 'configuration options for Goatherd\Session\SaveHandler\Mongo.');
        }
        foreach ($config as $key => $value) {
            switch ($key) {
                case self::NAME:
                    $this->_name = (string) $value;
                    break;
                        case self::MODIFIED_COLUMN:
                        $this->_modifiedColumn = (string) $value;
                        break;
                    case self::LIFETIME_COLUMN:
                        $this->_lifetimeColumn = (string) $value;
                        break;
                    case self::DATA_COLUMN:
                        $this->_dataColumn = (string) $value;
                        break;
                    case self::LIFETIME:
                        $this->setLifetime($value);
                        break;
                    default:
                    throw new \Zend_Session_SaveHandler_Exception(
                        sprintf(
                        'Unrecognised option "%s" for Goatherd\Session\SaveHandler\Mongo.',
                        $key)
                    );
            }
        }
    }

    /**
     * Destructor
     *
     * @return void
     */
    public function __destruct() {
        \Zend_Session::writeClose();
    }

    /**
     * Open a mongodb connection
     *
     * @return Mongo $_conn
     */
    public function getConnection()
    {
        if (null === $this->_conn) {
            throw new \Zend_Session_SaveHandler_Exception('No connection set.');
        }
        return $this->_conn;
    }

    /**
     *
     * @param \MongoDb $conn
     * @throws Exception
     */
    public function setConnection(\MongoDb $conn)
    {
        if (null !== $this->_conn) {
            throw new \Zend_Session_SaveHandler_Exception('Connection already set.');
        }

        $this->_conn = $conn;
        $this->_db = $this->_conn->php;
        $this->_collection = $this->_db->sessions;

        return $this;
    }

    /**
     * Set session lifetime.
     *
     * $lifetime == false represents session.gc_maxlifetime.
     *
     * @param int $lifetime
     */
    public function setLifetime($lifetime)
    {
        if ($lifetime < 0) {
            throw new \Zend_Session_SaveHandler_Exception();
        } else if (empty($lifetime)) {
            $this->_lifetime = (int) ini_get('session.gc_maxlifetime');
        } else {
            $this->_lifetime = (int) $lifetime;
        }

        return $this;
    }

    /**
    * Retrieve session lifetime
    *
    * @return int
    */
    public function getLifetime()
    {
        return $this->_lifetime;
    }

    /**
     *
     * (non-PHPdoc)
     * @see Zend_Session_SaveHandler_Interface::open()
     */
    public function open($save_path, $name) {
        $this->_sessionSavePath = $save_path;
        $this->_sessionName     = $name;

        return true;
    }

    /**
     *
     * (non-PHPdoc)
     * @see Zend_Session_SaveHandler_Interface::close()
     */
    public function close() {
        return true;
    }

    /**
     *
     * (non-PHPdoc)
     * @see Zend_Session_SaveHandler_Interface::read()
     */
    public function read($id) {
        $return = '';

        $session = $this->_collection->findOne(array($this->_idColumn=>$id));
        if(null !== $session) {
            if ($this->_getExpirationTime($session) > time()) {
                $return = $session[$this->_dataColumn];
            } else {
                $this->destroy($id);
            }
        }

        return $return;
    }

    /**
     *
     * (non-PHPdoc)
     * @see Zend_Session_SaveHandler_Interface::write()
     */
    public function write($id, $data) {
        $return = false;

        $return = $this->_collection->update(
                    array($this->_idColumn => $id),
                    array(
                       $this->_idColumn       => $id,
                       $this->_modifiedColumn => new \MongoDate(time),
                       $this->_lifetimeColumn => $this->_lifetime,
                       $this->_dataColumn     => (string) $data
                    ),
                    array("upsert" => true));

        return $return;
    }

    /**
     *
     * (non-PHPdoc)
     * @see Zend_Session_SaveHandler_Interface::destroy()
     */
    public function destroy($id) {
        $return = $this->_collection->remove(
                    array($this->_idColumn => $id),
                    array("justOne" => true));
        return $return;
    }

    /**
     *
     * (non-PHPdoc)
     * @see Zend_Session_SaveHandler_Interface::gc()
     */
    public function gc($maxlifetime) {
        $maxlifetime += time();
        $where = array(
            $this->_lifetimeColumn =>
                array( '$lte' => new \MongoDate($maxlifetime) ) );
        $this->_collection->remove($where);

        return true;
    }

    /**
     * Retrieve session lifetime
     *
     * @param array $data
     * @return int
     */
    protected function _getLifetime(array $data)
    {
        return $this->_lifetime;
    }

    /**
     * Retrieve session expiration time
     *
     * @param array $data
     * @return int
     */
    protected function _getExpirationTime(array $data)
    {
        return (int) $data[$this->_modifiedColumn] + $this->_getLifetime($data);
    }
}